<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>前后端分离项目认证方式详解</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        :root {
            --primary: #4f46e5;
            --primary-dark: #4338ca;
            --secondary: #10b981;
            --dark: #1e293b;
            --light: #f8fafc;
            --gray: #64748b;
            --gray-light: #e2e8f0;
        }
        
        body {
            font-family: 'Noto Sans SC', 'Helvetica Neue', Arial, sans-serif;
            color: var(--dark);
            line-height: 1.6;
            background-color: #f9fafb;
        }
        
        h1, h2, h3, h4, h5 {
            font-family: 'Noto Serif SC', serif;
            font-weight: 700;
            color: var(--dark);
        }
        
        .hero {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
        
        .card {
            transition: all 0.3s ease;
            border-radius: 12px;
            overflow: hidden;
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
        }
        
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
        }
        
        .code-block {
            font-family: 'Courier New', Courier, monospace;
            background-color: #1e293b;
            color: #f8fafc;
            border-radius: 8px;
            padding: 1.5rem;
            position: relative;
        }
        
        .code-block:before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            width: 4px;
            height: 100%;
            background: var(--primary);
        }
        
        .highlight {
            position: relative;
        }
        
        .highlight:after {
            content: "";
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 30%;
            background: rgba(79, 70, 229, 0.2);
            z-index: -1;
        }
        
        .diagram-container {
            background-color: white;
            border-radius: 12px;
            padding: 2rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
        }
        
        .btn {
            transition: all 0.3s ease;
        }
        
        .btn:hover {
            transform: translateY(-2px);
        }
        
        .feature-icon {
            width: 48px;
            height: 48px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 12px;
            background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
            color: white;
            font-size: 1.5rem;
        }
        
        .list-item {
            position: relative;
            padding-left: 2rem;
        }
        
        .list-item:before {
            content: "•";
            position: absolute;
            left: 0;
            color: var(--primary);
            font-weight: bold;
            font-size: 1.5rem;
        }
        
        footer {
            background-color: #1e293b;
            color: white;
        }
        
        footer a:hover {
            color: var(--gray-light);
        }
    </style>
</head>
<body>
    <!-- Hero Section -->
    <section class="hero text-white py-20">
        <div class="container mx-auto px-6 md:px-12">
            <div class="max-w-4xl mx-auto text-center">
                <h1 class="text-4xl md:text-5xl font-bold mb-6">前后端分离项目认证方式详解</h1>
                <p class="text-xl md:text-2xl mb-8">深入解析现代Web应用中的认证与授权机制</p>
                <div class="flex flex-wrap justify-center gap-4">
                    <a href="#session" class="btn bg-white text-primary px-6 py-3 rounded-lg font-semibold">Session认证</a>
                    <a href="#token" class="btn bg-white text-primary px-6 py-3 rounded-lg font-semibold">Token认证</a>
                    <a href="#oauth2" class="btn bg-white text-primary px-6 py-3 rounded-lg font-semibold">OAuth2认证</a>
                </div>
            </div>
        </div>
    </section>

    <!-- Main Content -->
    <main class="container mx-auto px-6 md:px-12 py-12">
        <!-- Introduction -->
        <section class="mb-20">
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
                <div>
                    <h2 class="text-3xl font-bold mb-6">认证/授权的基本概念</h2>
                    <p class="text-lg text-gray-700 mb-6">在现代Web应用中，认证与授权是保障系统安全的两大基石。了解它们的区别与实现方式对于开发安全的应用程序至关重要。</p>
                    
                    <div class="bg-white rounded-xl shadow-md p-6 mb-6">
                        <h3 class="text-xl font-semibold mb-4 flex items-center">
                            <span class="feature-icon mr-4"><i class="fas fa-user-shield"></i></span>
                            什么是认证
                        </h3>
                        <p class="mb-4">在互联网中，我们每天都会使用到各种各样的APP和网站，在使用过程中通常还会遇到需要注册登录的情况，输入你的用户名和密码才能正常使用，也就是说成为这个应用的合法身份才可以访问应用的资源，这个过程就是认证。</p>
                        <p class="font-semibold mb-2">认证方式包括：</p>
                        <ul class="space-y-2">
                            <li class="list-item">用户名密码登录</li>
                            <li class="list-item">邮箱发送登录链接</li>
                            <li class="list-item">手机号接收验证码</li>
                            <li class="list-item">只要你能收到邮箱/验证码，就默认你是账号的主人</li>
                        </ul>
                    </div>
                    
                    <div class="bg-white rounded-xl shadow-md p-6">
                        <h3 class="text-xl font-semibold mb-4 flex items-center">
                            <span class="feature-icon mr-4"><i class="fas fa-key"></i></span>
                            什么是授权
                        </h3>
                        <p class="mb-4">授权是用户认证通过根据用户的权限来控制用户访问资源的过程，拥有资源的访问权限则正常访问，没有权限则拒绝访问。例如视频网站的VIP用户，可以查看到普通用户看不到的资源信息。</p>
                        <p class="font-semibold mb-2">授权场景包括：</p>
                        <ul class="space-y-2">
                            <li class="list-item">你在安装手机应用的时候，APP会询问是否允许授予权限（访问相册、地理位置等权限）</li>
                            <li class="list-item">你在访问微信小程序时，当登录时，小程序会询问是否允许授予权限（获取昵称、头像、地区、性别等个人信息）</li>
                        </ul>
                    </div>
                </div>
                
                <div class="diagram-container">
                    <div class="mermaid">
                        flowchart TD
                            A[用户] -->|1. 请求访问| B(前端应用)
                            B -->|2. 重定向到认证| C{认证服务器}
                            C -->|3. 用户认证| A
                            A -->|4. 提供凭证| C
                            C -->|5. 颁发Token| B
                            B -->|6. 使用Token请求| D[API服务]
                            D -->|7. 验证Token| C
                            C -->|8. 返回验证结果| D
                            D -->|9. 返回数据| B
                            B -->|10. 展示数据| A
                    </div>
                </div>
            </div>
        </section>

        <!-- Session Authentication -->
        <section id="session" class="mb-20">
            <h2 class="text-3xl font-bold mb-8 text-center">基于Session认证</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <div class="card bg-white p-6 mb-8">
                        <h3 class="text-2xl font-semibold mb-4 flex items-center">
                            <i class="fas fa-server text-primary mr-3"></i>
                            Session认证原理
                        </h3>
                        <p class="mb-4">用户认证成功后，在服务端生成用户相关的数据保存在当前会话session(服务端)中，发给客户端的SessionId会存放到cookie中，这样用户客户端请求时带上SessionId就可以验证服务器端是否存在session数据，以此完成用户的合法校验。</p>
                        <p>当用户退出系统或session过期销毁时，客户端的SessionId也就无效了。</p>
                    </div>
                    
                    <div class="card bg-white p-6">
                        <h3 class="text-2xl font-semibold mb-4 flex items-center">
                            <i class="fas fa-list-check text-primary mr-3"></i>
                            Session特点
                        </h3>
                        <ul class="space-y-3">
                            <li class="list-item">Session是存储在服务器端的安全性较高</li>
                            <li class="list-item">Session底层通过Cookie实现，如果客户端不支持Cookie，Session将无法实现</li>
                            <li class="list-item">Session在服务集群的情况下需要同步session的状态</li>
                            <li class="list-item">Session可以保存用户的信息，可以设置超时时间</li>
                        </ul>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6 h-full">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652887564536-f97851a4-bb7c-4c4c-89f1-7365c734232d.png" alt="Session认证流程图" class="rounded-lg w-full h-auto object-cover">
                    </div>
                </div>
            </div>
        </section>

        <!-- Token Authentication -->
        <section id="token" class="mb-20">
            <h2 class="text-3xl font-bold mb-8 text-center">基于Token认证</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-2xl font-semibold mb-4 flex items-center">
                            <i class="fas fa-user-lock text-primary mr-3"></i>
                            Token认证原理
                        </h3>
                        <p class="mb-4">用户认证成功后，服务端生成一个token发给客户端，客户端可以放到cookie或localStorage等存储中，每次请求时带上token，服务端收到token通过验证后即可确认用户身份。</p>
                        
                        <h4 class="text-xl font-semibold mt-6 mb-3">Token特点</h4>
                        <ul class="space-y-3">
                            <li class="list-item">访问资源的令牌</li>
                            <li class="list-item">可以记录用户的信息</li>
                            <li class="list-item">服务端只做校验无状态</li>
                            <li class="list-item">只有验证成功后，客户端才能访问服务端上受保护的资源</li>
                            <li class="list-item">保存在客户端，不受客户端的影响</li>
                        </ul>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6 mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652887580977-85515d13-b5c7-4055-aa63-08edc935c396.png" alt="Token认证流程图" class="rounded-lg w-full h-auto object-cover">
                    </div>
                    
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">单Token机制实现</h3>
                        <div class="code-block mb-4">
                            <pre><code class="text-white">public class LoginUser implements Serializable {
    private String token;
    private Integer userid;
    private String username;
    private Long loginTime;
    private Long expireTime;
    private String ipaddr;
    private Set&lt;String&gt; permissions;
    private Set&lt;String&gt; roles;
    private User user;
}</code></pre>
                        </div>
                        <p class="text-sm text-gray-600">登录用户实体类封装，包含用户信息和token相关信息</p>
                    </div>
                </div>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8">
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">登录实现流程</h3>
                        <div class="code-block">
                            <pre><code class="text-white">@PostMapping("/login")
public R login(@RequestBody Map&lt;String, String&gt; param) {
    // 1.登录
    User user = loginService.login(param);
    
    // 2.创建token
    String token = tokenService.createToken(user);
    
    // 3、把token返回客户端
    return R.ok().put("token", token);
}</code></pre>
                        </div>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">Token校验拦截器</h3>
                        <div class="code-block">
                            <pre><code class="text-white">@Override
public boolean preHandle(HttpServletRequest request, 
    HttpServletResponse response, Object handler) {
    
    // 1.从请求头中获取token
    String token = request.getHeader("token");
    
    // 2.token非空校验
    if (ObjectUtils.isEmpty(token)) {
        throw new UserTokenIsNullException(1001, "token不能为空");
    }
    
    // 3.校验Token的合法性
    LoginUser loginUser = tokenService.getLoginUser(token);
    if (loginUser == null) {
        throw new BusinessException(1003, "token有误，请重新登录");
    }
    
    UserContextHolder.set(loginUser);
    return true;
}</code></pre>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Double Token Mechanism -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8 text-center">双Token机制</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-2xl font-semibold mb-4 flex items-center">
                            <i class="fas fa-sync-alt text-primary mr-3"></i>
                            双Token机制原理
                        </h3>
                        <p class="mb-4">双Token机制使用access_token和refresh_token两个令牌：</p>
                        <ul class="space-y-3 mb-6">
                            <li class="list-item"><span class="font-semibold">access_token:</span> 用于访问资源的短期令牌</li>
                            <li class="list-item"><span class="font-semibold">refresh_token:</span> 用于刷新access_token的长期令牌</li>
                        </ul>
                        
                        <h4 class="text-xl font-semibold mb-3">为什么要使用refreshToken?</h4>
                        <ul class="space-y-3">
                            <li class="list-item">在accessToken过期以后，用户将会被强制重新登录，影响用户体验</li>
                            <li class="list-item">accessToken过期时太长，被盗后还可以使用</li>
                            <li class="list-item">refresh token只有在第一次获取和刷新access token时才会在网络中传输，因此被盗的风险远小于access token</li>
                        </ul>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653134325565-6167f317-c969-4a15-82e3-2aae24f06a8f.png" alt="双Token机制流程图" class="rounded-lg w-full h-auto object-cover">
                    </div>
                </div>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8">
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">双Token登录实现</h3>
                        <div class="code-block">
                            <pre><code class="text-white">@PostMapping("/login")
public R login(@RequestBody LoginUserVo loginUserVo) {
    // 1、判断非空
    if (ObjectUtils.isEmpty(loginUserVo.getUsername()) || 
        ObjectUtils.isEmpty(loginUserVo.getPassword())) {
        return R.error("用户名获密码为空");
    }

    // 2.验证用户信息
    UserDto userEntity = getUserByUsername(loginUserVo.getUsername());
    if (userEntity == null || !userEntity.getPassword()
        .equals(loginUserVo.getPassword())) {
        return R.error("用户名或密码错误");
    }

    // 3.创建双Token
    UserTokenDto userToken = new UserTokenDto();
    userToken.setUserId(userEntity.getUserId());
    userToken.setUsername(loginUserVo.getUsername());
    userToken.setCreateTime(System.currentTimeMillis());
    
    String acessToken = JwtUtils.createAccessToken(userToken);
    String refershToken = JwtUtils.createRefershToken(userToken);

    // 4.返回两个token给客户端
    return R.ok().put("accessToken", acessToken)
                .put("refershToken", refershToken);
}</code></pre>
                        </div>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">前端Token刷新机制</h3>
                        <div class="code-block">
                            <pre><code class="text-white">export default (url, method, data) => {
    let header = {};
    if (url.indexOf("refreshToken") != -1) {
        // 携带refreshToken请求新accessToken
        const refreshToken = uni.getStorageSync('refreshToken');
        header.refreshToken = refreshToken;
    } else {
        // 普通请求携带accessToken
        const accessToken = uni.getStorageSync('accessToken');
        header.accessToken = accessToken;
    }

    return new Promise((resolve, reject) => {
        uni.request({
            url: API_BASE + url,
            method: method,
            header,
            data,
            success(res) {
                if (res.data.code == 4001) { // accessToken失效
                    api.refreshToken().then((resp) => {
                        if (resp.code == 500) { // 刷新失败
                            uni.redirectTo({ url: "/login" })
                        } else if (resp.code == 0) { // 刷新成功
                            uni.setStorageSync("accessToken", resp.accessToken);
                            // 重新加载当前页面
                            const pages = getCurrentPages();
                            const curPage = pages[pages.length - 1];
                            curPage.onLoad(curPage.options)
                        }
                    });
                } else {
                    resolve(res.data);
                }
            }
        });
    });
};</code></pre>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- OAuth2 Authentication -->
        <section id="oauth2" class="mb-20">
            <h2 class="text-3xl font-bold mb-8 text-center">基于OAuth2认证</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-2xl font-semibold mb-4 flex items-center">
                            <i class="fas fa-shield-alt text-primary mr-3"></i>
                            OAuth2认证原理
                        </h3>
                        <p class="mb-4">OAuth2是一种开放授权标准，允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息，而不需要将用户名和密码提供给第三方应用。</p>
                        
                        <h4 class="text-xl font-semibold mt-6 mb-3">OAuth2角色</h4>
                        <ul class="space-y-3">
                            <li class="list-item"><span class="font-semibold">Resource Owner:</span> 资源所有者，即用户</li>
                            <li class="list-item"><span class="font-semibold">Authorization Server:</span> 授权服务器，验证用户身份并颁发令牌</li>
                            <li class="list-item"><span class="font-semibold">Resource Server:</span> 资源服务器，托管受保护的用户数据</li>
                            <li class="list-item"><span class="font-semibold">Client:</span> 客户端，即第三方应用</li>
                        </ul>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1653127011526-d437eb77-1fee-4767-9cf7-00a9f9bf0502.png" alt="OAuth2流程图" class="rounded-lg w-full h-auto object-cover">
                    </div>
                </div>
            </div>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8">
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">OAuth2特点</h3>
                        <ul class="space-y-3">
                            <li class="list-item">客户端不接触用户密码，服务器端更易集中保护</li>
                            <li class="list-item">资源服务器和授权服务器解耦</li>
                            <li class="list-item">集中式第三方授权</li>
                        </ul>
                    </div>
                </div>
                
                <div>
                    <div class="card bg-white p-6">
                        <h3 class="text-xl font-semibold mb-4">OAuth2认证流程</h3>
                        <div class="mermaid">
                            sequenceDiagram
                                participant User
                                participant Client
                                participant AuthServer
                                participant ResourceServer
                                
                                User->>Client: 访问客户端
                                Client->>AuthServer: 重定向到授权页面
                                AuthServer->>User: 显示授权页面
                                User->>AuthServer: 授权同意
                                AuthServer->>Client: 返回授权码
                                Client->>AuthServer: 使用授权码请求token
                                AuthServer->>Client: 返回access_token
                                Client->>ResourceServer: 使用token请求资源
                                ResourceServer->>Client: 返回受保护资源
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Comparison Section -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8 text-center">认证方式对比</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                <div class="card bg-white p-6">
                    <div class="text-center mb-6">
                        <i class="fas fa-server text-4xl text-primary mb-4"></i>
                        <h3 class="text-xl font-semibold">Session认证</h3>
                    </div>
                    <ul class="space-y-3">
                        <li class="list-item">服务端存储状态</li>
                        <li class="list-item">依赖Cookie</li>
                        <li class="list-item">集群环境下需要同步</li>
                        <li class="list-item">安全性较高</li>
                    </ul>
                </div>
                
                <div class="card bg-white p-6">
                    <div class="text-center mb-6">
                        <i class="fas fa-key text-4xl text-primary mb-4"></i>
                        <h3 class="text-xl font-semibold">Token认证</h3>
                    </div>
                    <ul class="space-y-3">
                        <li class="list-item">无状态</li>
                        <li class="list-item">客户端存储</li>
                        <li class="list-item">适合分布式系统</li>
                        <li class="list-item">支持跨域</li>
                    </ul>
                </div>
                
                <div class="card bg-white p-6">
                    <div class="text-center mb-6">
                        <i class="fas fa-share-square text-4xl text-primary mb-4"></i>
                        <h3 class="text-xl font-semibold">OAuth2认证</h3>
                    </div>
                    <ul class="space-y-3">
                        <li class="list-item">第三方授权</li>
                        <li class="list-item">不暴露用户凭证</li>
                        <li class="list-item">标准化流程</li>
                        <li class="list-item">适合开放平台</li>
                    </ul>
                </div>
            </div>
        </section>
    </main>

    <!-- Footer -->
    <footer class="py-8">
        <div class="container mx-auto px-6 md:px-12 text-center">
            <p class="text-gray-300 mb-2">技术小馆</p>
            <a href="http://www.yuque.com/jtostring" class="text-gray-300 hover:text-white transition-colors">http://www.yuque.com/jtostring</a>
        </div>
    </footer>

    <script>
        // Initialize Mermaid.js
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: false,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>